package sin.utils;

import java.io.Closeable;
import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.Flushable;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.io.Writer;
import java.lang.reflect.InvocationTargetException;
import java.nio.channels.FileChannel;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;

import org.apache.commons.beanutils.BeanUtils;

/**
 * Swiss Army Knife 瑞士军刀，一些常用的方法塞进来
 * 
 * @author francis
 */
public class SakUtils {

	public static final long ONE_KB = 1024;
	public static final long ONE_MB = ONE_KB * ONE_KB;
	private static final int SKIP_BUFFER_SIZE = 2048;
	private static final long FILE_COPY_BUFFER_SIZE = ONE_MB * 30;
	private static final int DEFAULT_BUFFER_SIZE = 1024 * 4;
	private static final int EOF = -1;
	private static byte[] SKIP_BYTE_BUFFER;

	public static <K, V> Map<K, V> newHashMap() {
		return new HashMap<K, V>();
	}

	/**
	 * 将key value转为map
	 * 
	 * @param keyValues
	 * @return
	 */
	public static Map<String, Object> toMap(Object... keyValues) {
		Map<String, Object> map = newHashMap();
		return newHashMap(map, keyValues);
	}

	@SuppressWarnings("unchecked")
	public static <K, V> Map<K, V> newHashMap(Map<K, V> orginMap,
			Object... keyValues) {
		Map<K, V> map = new HashMap<K, V>(orginMap);
		if (keyValues == null || keyValues.length < 2)
			return map;
		for (int i = 0; i < keyValues.length; i += 2) {
			map.put((K) keyValues[i], (V) keyValues[i + 1]);
		}
		return map;
	}

	public static boolean notIn(String find, String... values) {
		for (String value : values) {
			if (find.equals(value))
				return false;
		}
		return true;
	}

	public static boolean notIn(int find, int... values) {
		for (int value : values) {
			if (find == value)
				return false;
		}
		return true;
	}

	public static String join(String[] strs, String separator) {
		if (strs == null)
			return null;
		StringBuilder sb = new StringBuilder();
		for (int i = 0; i < strs.length; i++) {
			sb.append(strs[i]);
			if (i < strs.length - 1)
				sb.append(separator);
		}
		return sb.toString();
	}

	public static String join(int[] strs, String separator) {
		if (strs == null)
			return null;
		StringBuilder sb = new StringBuilder();
		for (int i = 0; i < strs.length; i++) {
			sb.append(strs[i]);
			if (i < strs.length - 1)
				sb.append(separator);
		}
		return sb.toString();
	}

	public static String join(List<String> strs, String separator) {
		if (strs == null)
			return null;
		StringBuilder sb = new StringBuilder();
		for (int i = 0; i < strs.size(); i++) {
			sb.append(strs.get(i));
			if (i < strs.size() - 1)
				sb.append(separator);
		}
		return sb.toString();
	}

	public static String toIn(List<String> ids) {
		if (ids == null || ids.size() == 0)
			return null;
		return "'" + join(ids, "','") + "'";
	}

	public static String toIn(String... ids) {
		if (ids == null || ids.length == 0)
			return null;
		return "'" + join(ids, "','") + "'";
	}

	public static String toIn(int... ids) {
		if (ids == null || ids.length == 0)
			return null;
		return join(ids, ",");
	}

	public static boolean isNotBlank(Object obj) {
		return !isBlank(obj);
	}

	public static boolean isBlank(Object obj) {
		String str = String.valueOf(obj);
		int strLen;
		if (obj == null || str == null || (strLen = str.length()) == 0) {
			return true;
		}
		for (int i = 0; i < strLen; i++) {
			if ((Character.isWhitespace(str.charAt(i)) == false)) {
				return false;
			}
		}
		return true;
	}

	public static boolean in(String find, String... values) {
		for (String value : values) {
			if (find.equals(value))
				return true;
		}
		return false;
	}

	public static boolean in(int find, int... values) {
		for (int value : values) {
			if (find == value)
				return true;
		}
		return false;
	}

	public static <E> List<E> newList() {
		return new ArrayList<E>();
	}
	
	public static <E> List<E> newList(E e) {
		List<E> list = new ArrayList<E>();
		list.add(e);
		return list;
	}
	
	

	public static <E> HashSet<E> newHashSet() {
		return new HashSet<E>();
	}

	public static void createParentDirs(File file) throws IOException {
		checkNotNull(file);
		File parent = file.getCanonicalFile().getParentFile();
		if (parent == null)
			return;

		parent.mkdirs();
		if (!parent.isDirectory()) {
			throw new IOException("Unable to create parent directories of "
					+ file);
		}
	}

	public static boolean isEmail(String email) {
		return email != null
				&& Pattern.compile("\\w+@(\\w+.)+[a-z]{2,3}").matcher(email)
						.matches();
	}

	public static void createDirs(File file) {
		checkNotNull(file);
		File parent = null;
		try {
			parent = file.getCanonicalFile().getParentFile();
			if (parent == null)
				return;

			parent.mkdirs();
		} catch (IOException e) {
			e.printStackTrace();
		}
		if (parent != null && !parent.isDirectory()) {
			throw new RuntimeException(
					"Unable to create parent directories of " + file);
		}
	}

	public static void createFile(File file) {
		try {
			createDirs(file);
			file.createNewFile();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public static void mkdir(File folder) {
		createDirs(folder);
		folder.mkdir();
	}

	public static void copyFile(File srcFile, File destFile) {
		try {
			copyFile(srcFile, destFile, true);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public static void copyFile(File srcFile, File destFile,
			boolean preserveFileDate) throws IOException {
		if (srcFile == null) {
			throw new NullPointerException("Source must not be null");
		}
		if (destFile == null) {
			throw new NullPointerException("Destination must not be null");
		}
		if (srcFile.exists() == false) {
			throw new FileNotFoundException("Source '" + srcFile
					+ "' does not exist");
		}
		if (srcFile.isDirectory()) {
			throw new IOException("Source '" + srcFile
					+ "' exists but is a directory");
		}
		if (srcFile.getCanonicalPath().equals(destFile.getCanonicalPath())) {
			throw new IOException("Source '" + srcFile + "' and destination '"
					+ destFile + "' are the same");
		}
		File parentFile = destFile.getParentFile();
		if (parentFile != null) {
			if (!parentFile.mkdirs() && !parentFile.isDirectory()) {
				throw new IOException("Destination '" + parentFile
						+ "' directory cannot be created");
			}
		}
		if (destFile.exists() && destFile.canWrite() == false) {
			throw new IOException("Destination '" + destFile
					+ "' exists but is read-only");
		}
		doCopyFile(srcFile, destFile, preserveFileDate);
	}

	public static long copyFile(File input, OutputStream output)
			throws IOException {
		final FileInputStream fis = new FileInputStream(input);
		try {
			return copyLarge(fis, output);
		} finally {
			fis.close();
		}
	}

	private static void doCopyFile(File srcFile, File destFile,
			boolean preserveFileDate) throws IOException {
		if (destFile.exists() && destFile.isDirectory()) {
			throw new IOException("Destination '" + destFile
					+ "' exists but is a directory");
		}

		FileInputStream fis = null;
		FileOutputStream fos = null;
		FileChannel input = null;
		FileChannel output = null;
		try {
			fis = new FileInputStream(srcFile);
			fos = new FileOutputStream(destFile);
			input = fis.getChannel();
			output = fos.getChannel();
			long size = input.size();
			long pos = 0;
			long count = 0;
			while (pos < size) {
				count = size - pos > FILE_COPY_BUFFER_SIZE ? FILE_COPY_BUFFER_SIZE
						: size - pos;
				pos += output.transferFrom(input, pos, count);
			}
		} finally {
			closeQuietly(output);
			closeQuietly(fos);
			closeQuietly(input);
			closeQuietly(fis);
		}

		if (srcFile.length() != destFile.length()) {
			throw new IOException("Failed to copy full contents from '"
					+ srcFile + "' to '" + destFile + "'");
		}
		if (preserveFileDate) {
			destFile.setLastModified(srcFile.lastModified());
		}
	}

	public static <T> T checkNotNull(T reference) {
		if (reference == null) {
			throw new NullPointerException();
		}
		return reference;
	}

	public static String formatNum(double i, int suflen) {
		NumberFormat nf = NumberFormat.getNumberInstance();
		nf.setMaximumFractionDigits(suflen);
		nf.setMinimumFractionDigits(suflen);
		nf.setGroupingUsed(false);
		return nf.format(i);
	}

	public static String formatNum(double i) {
		return formatNum(i, 2);
	}

	public static String getFormatLength(File file) {
		return getFormatLength(file == null ? 0 : file.length());
	}

	public static String getFormatLength(String path) {
		File file = new File(path);
		return getFormatLength(file);
	}

	/**
	 * 自己封装的计算上传文件的大小并格式化 以设置为文件属性
	 */
	public static String getFormatLength(long size) {
		Double a = Double.valueOf(size);
		StringBuffer sb = new StringBuffer();
		DecimalFormat format = new DecimalFormat("#0.00");
		if (a >= 1024 * 1024 * 1024) {
			a = a / 1024 / 1024 / 1024 * 1.00;
			sb.append(format.format(a)).append(" GB");
		} else if (a >= 1024 * 1024 && a < 1024 * 1024 * 1024) {
			a = a / 1024 / 1024 * 1.00;
			sb.append(format.format(a)).append(" MB");
		} else if (a >= 1024 && a < 1024 * 1024) {
			a = a / 1024 * 1.00;
			sb.append(format.format(a)).append(" KB");
		} else {
			sb.append(format.format(a)).append(" 字节");
		}

		return sb.toString();
	}

	/**
	 * 数字转成人民币，最多支持17位数字（包含小数点以及小数位数）
	 * http://blog.csdn.net/fjfdszj/article/details/4320190
	 * 
	 * @param value
	 * @return
	 */
	public static String toRMB(double value) {
		char[] hunit = { '拾', '佰', '仟' };// 段内位置表示
		char[] vunit = { '万', '亿' }; // 段名表示
		char[] digit = { '零', '壹', '贰', '叁', '肆', '伍', '陆', '柒', '捌', '玖' }; // 数字表示
		long midVal = (long) (value * 100); // 转化成整形
		String valStr = String.valueOf(midVal); // 转化成字符串
		String head = valStr.substring(0, valStr.length() - 2); // 取整数部分
		String rail = valStr.substring(valStr.length() - 2); // 取小数部分
		String prefix = ""; // 整数部分转化的结果
		String suffix = ""; // 小数部分转化的结果

		if (valStr.length() > 17)
			throw new RuntimeException("数值过大！");// 解决问题1,超过千亿的问题。

		// 处理小数点后面的数
		if (rail.equals("00")) { // 如果小数部分为0
			suffix = "整";
		} else {
			// 处理零角,零分发音问题 2010-06-03
			if (rail.charAt(0) != '0')
				suffix = digit[rail.charAt(0) - '0'] + "角";
			if (rail.charAt(1) != '0')
				suffix += digit[rail.charAt(1) - '0'] + "分";
		}
		// 处理小数点前面的数
		char[] chDig = head.toCharArray(); // 把整数部分转化成字符数组
		char zero = '0'; // 标志'0'表示出现过0
		byte zeroSerNum = 0; // 连续出现0的次数
		for (int i = 0; i < chDig.length; i++) { // 循环处理每个数字
			int idx = (chDig.length - i - 1) % 4; // 取段内位置
			int vidx = (chDig.length - i - 1) / 4; // 取段位置
			if (chDig[i] == '0') { // 如果当前字符是0
				zeroSerNum++; // 连续0次数递增
				if (zero == '0' && idx != 0) { // 标志 ,连续零，仅读一次零，
					zero = digit[0]; // 解决问题2,当一个零位于第0位时，不输出“零”，仅输出“段名”.
				} else if (idx == 0 && vidx > 0 && zeroSerNum < 4) {
					prefix += vunit[vidx - 1];
					zero = '0';
				}
				continue;
			}
			zeroSerNum = 0; // 连续0次数清零
			if (zero != '0') { // 如果标志不为0,则加上,例如万,亿什么的
				prefix += zero;
				zero = '0';
			}
			// 取到该位对应数组第几位。
			int position = chDig[i] - '0';
			if (position == 1 && i == 0 && idx == 1)// 解决问题3 ,即处理10读"拾",而不读"壹拾"
			{
			} else {
				prefix += digit[position]; // 转化该数字表示
			}

			if (idx > 0)
				prefix += hunit[idx - 1];// 段内位置表示的值

			if (idx == 0 && vidx > 0)
				prefix += vunit[vidx - 1]; // 段结束位置应该加上段名如万,亿
		}

		if (prefix.length() > 0)
			prefix += '元'; // 如果整数部分存在,则有圆的字样

		return prefix + suffix; // 返回正确表示
	}

	public static String format(Date date, String formatStr) {
		SimpleDateFormat format = new SimpleDateFormat(formatStr);
		return format.format(date);
	}

	public static int copy(InputStream input, OutputStream output)
			throws IOException {
		long count = copyLarge(input, output);
		if (count > Integer.MAX_VALUE) {
			return -1;
		}
		return (int) count;
	}

	public static long copyLarge(InputStream input, OutputStream output)
			throws IOException {
		return copyLarge(input, output, new byte[DEFAULT_BUFFER_SIZE]);
	}

	public static long copyLarge(InputStream input, OutputStream output,
			byte[] buffer) throws IOException {
		long count = 0;
		int n = 0;
		while (EOF != (n = input.read(buffer))) {
			output.write(buffer, 0, n);
			count += n;
		}
		return count;
	}

	public static long copyLarge(InputStream input, OutputStream output,
			long inputOffset, long length) throws IOException {
		return copyLarge(input, output, inputOffset, length,
				new byte[DEFAULT_BUFFER_SIZE]);
	}

	public static long copyLarge(InputStream input, OutputStream output,
			final long inputOffset, final long length, byte[] buffer)
			throws IOException {
		if (inputOffset > 0) {
			skipFully(input, inputOffset);
		}
		if (length == 0) {
			return 0;
		}
		final int bufferLength = buffer.length;
		int bytesToRead = bufferLength;
		if (length > 0 && length < bufferLength) {
			bytesToRead = (int) length;
		}
		int read;
		long totalRead = 0;
		while (bytesToRead > 0
				&& EOF != (read = input.read(buffer, 0, bytesToRead))) {
			output.write(buffer, 0, read);
			totalRead += read;
			if (length > 0) {
				bytesToRead = (int) Math.min(length - totalRead, bufferLength);
			}
		}
		return totalRead;
	}

	public static void skipFully(InputStream input, long toSkip)
			throws IOException {
		if (toSkip < 0) {
			throw new IllegalArgumentException(
					"Bytes to skip must not be negative: " + toSkip);
		}
		long skipped = skip(input, toSkip);
		if (skipped != toSkip) {
			throw new EOFException("Bytes to skip: " + toSkip + " actual: "
					+ skipped);
		}
	}

	public static long skip(InputStream input, long toSkip) throws IOException {
		if (toSkip < 0) {
			throw new IllegalArgumentException(
					"Skip count must be non-negative, actual: " + toSkip);
		}
		if (SKIP_BYTE_BUFFER == null) {
			SKIP_BYTE_BUFFER = new byte[SKIP_BUFFER_SIZE];
		}
		long remain = toSkip;
		while (remain > 0) {
			long n = input.read(SKIP_BYTE_BUFFER, 0,
					(int) Math.min(remain, SKIP_BUFFER_SIZE));
			if (n < 0) { // EOF
				break;
			}
			remain -= n;
		}
		return toSkip - remain;
	}

	public static void closeQuietly(Reader input) {
		closeQuietly((Closeable) input);
	}

	public static void closeQuietly(Writer output) {
		closeQuietly((Closeable) output);
	}

	public static void closeQuietly(InputStream input) {
		closeQuietly((Closeable) input);
	}

	public static void closeQuietly(OutputStream output) {
		closeQuietly((Closeable) output);
	}

	public static void closeQuietly(Closeable closeable) {
		try {
			if (closeable != null) {
				closeable.close();
			}
		} catch (IOException ioe) {
		}
	}
	
	public static void flushQuietly(Reader input) {
		flushQuietly((Flushable) input);
	}
	
	public static void flushQuietly(Writer output) {
		flushQuietly((Flushable) output);
	}
	
	public static void flushQuietly(InputStream input) {
		flushQuietly((Flushable) input);
	}
	
	public static void flushQuietly(OutputStream output) {
		flushQuietly((Flushable) output);
	}
	
	public static void flushQuietly(Flushable flushable) {
		try {
			if (flushable != null) {
				flushable.flush();
			}
		} catch (IOException ioe) {
		}
	}

	/**
	 * 获取List<Bean>中Bean对应的一个属性
	 * 
	 * @param beans
	 * @param column
	 * @return
	 */
	public static String[] getValues(List<?> beans, String column) {
		if (beans == null)
			return null;
		String[] results = new String[beans.size()];
		try {
			for (int i = 0; i < beans.size(); i++) {
				if (beans.get(i) != null)
					results[i] = String.valueOf(BeanUtils.getProperty(
							beans.get(i), column));
			}
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			e.printStackTrace();
		} catch (NoSuchMethodException e) {
			e.printStackTrace();
		}

		return results;

	}

	public static String getImageOfDoc(File file) {
		if (file == null || !file.exists())
			return null;

		InputStream in = null;
		byte[] data = null;
		try {
			in = new FileInputStream(file);
			data = new byte[in.available()];
			in.read(data);
			in.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
		return encodeImageToDoc(data);
	}

	private static char[] codec_table = { 'A', 'B', 'C', 'D', 'E', 'F', 'G',
			'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
			'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
			'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
			'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6',
			'7', '8', '9', '+', '/' };

	public static String encodeImageToDoc(byte[] a) {
		int totalBits = a.length * 8;
		int nn = totalBits % 6;
		int curPos = 0;// process bits
		StringBuffer toReturn = new StringBuffer();
		while (curPos < totalBits) {
			int bytePos = curPos / 8;
			switch (curPos % 8) {
			case 0:
				toReturn.append(codec_table[(a[bytePos] & 0xfc) >> 2]);
				break;
			case 2:

				toReturn.append(codec_table[(a[bytePos] & 0x3f)]);
				break;
			case 4:
				if (bytePos == a.length - 1) {
					toReturn.append(codec_table[((a[bytePos] & 0x0f) << 2) & 0x3f]);
				} else {
					int pos = (((a[bytePos] & 0x0f) << 2) | ((a[bytePos + 1] & 0xc0) >> 6)) & 0x3f;
					toReturn.append(codec_table[pos]);
				}
				break;
			case 6:
				if (bytePos == a.length - 1) {
					toReturn.append(codec_table[((a[bytePos] & 0x03) << 4) & 0x3f]);
				} else {
					int pos = (((a[bytePos] & 0x03) << 4) | ((a[bytePos + 1] & 0xf0) >> 4)) & 0x3f;
					toReturn.append(codec_table[pos]);
				}
				break;
			default:
				// never hanppen
				break;
			}
			curPos += 6;
		}
		if (nn == 2) {
			toReturn.append("==");
		} else if (nn == 4) {
			toReturn.append("=");
		}
		return toReturn.toString();

	}
}
